﻿using System;
using System.Drawing;
using System.Runtime.InteropServices;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using AnimatingHair.Rendering;
using System.IO;
using System.Collections.Generic;

namespace AnimatingHair
{
    static class Utility
    {
        public static void DrawSphere( double radius, int lats, int longs )
        {
            IntPtr quadric = GLU.NewQuadric();
            GLU.Sphere( quadric, radius, lats, longs );
            GLU.DeleteQuadric( quadric );
        }

        public static void RenderCylinder( double x1, double y1, double z1, double x2, double y2, double z2, double radius, int subdivisions )
        {
            double vx = x2 - x1;
            double vy = y2 - y1;
            double vz = z2 - z1;

            IntPtr quadric = GLU.NewQuadric();

            //handle the degenerate case of z1 == z2 with an approximation
            if ( vz == 0 )
                vz = .00000001;

            double v = Math.Sqrt( vx * vx + vy * vy + vz * vz );
            double ax = 57.2957795 * Math.Acos( vz / v );
            if ( vz < 0.0 )
                ax = -ax;
            double rx = -vy * vz;
            double ry = vx * vz;
            GL.PushMatrix();

            //draw the cylinder body
            GL.Translate( x1, y1, z1 );
            GL.Rotate( ax, rx, ry, 0.0 );
            //GLU.QuadricOrientation( quadric, GLU_OUTSIDE );
            GLU.Cylinder( quadric, radius, radius, v, subdivisions, 1 );

            //draw the first cap
            //gluQuadricOrientation( quadric, GLU_INSIDE );
            GLU.Disk( quadric, 0.0, radius, subdivisions, 1 );
            GL.Translate( 0, 0, v );

            //draw the second cap
            //gluQuadricOrientation( quadric, GLU_OUTSIDE );
            GLU.Disk( quadric, 0.0, radius, subdivisions, 1 );
            GL.PopMatrix();
        }

        public static TriangleMesh LoadOBJ( string path )
        {
            TriangleMesh result = new TriangleMesh( true );

            StreamReader objFile = new StreamReader( path );

            List<Vector3> vertexList = new List<Vector3>();
            List<Vector3> normalList = new List<Vector3>();
            List<Vector2> texCoordList = new List<Vector2>();
            List<TriangleIndex> triangleIndices = new List<TriangleIndex>();

            while ( !objFile.EndOfStream )
            {
                string line = objFile.ReadLine();

                string[] lineParts = line.Split( ' ' );

                switch ( lineParts[ 0 ] )
                {
                    case "v":
                        vertexList.Add( new Vector3(
                            float.Parse( lineParts[ 1 ] ),
                            float.Parse( lineParts[ 2 ] ),
                            float.Parse( lineParts[ 3 ] ) ) );
                        break;
                    case "vn":
                        normalList.Add( new Vector3(
                            float.Parse( lineParts[ 1 ] ),
                            float.Parse( lineParts[ 2 ] ),
                            float.Parse( lineParts[ 3 ] ) ) );
                        break;
                    case "vt":
                        texCoordList.Add( new Vector2(
                            float.Parse( lineParts[ 1 ] ),
                            float.Parse( lineParts[ 2 ] ) ) );
                        break;
                    case "f":
                        string[] indices1s = lineParts[ 1 ].Split( '/' );
                        int[] indices1 = {
                                             int.Parse( indices1s[ 0 ] ) - 1,
                                             int.Parse( indices1s[ 1 ] ) - 1,
                                             int.Parse( indices1s[ 2 ] ) - 1
                                         };
                        string[] indices2s = lineParts[ 2 ].Split( '/' );
                        int[] indices2 = {
                                             int.Parse( indices2s[ 0 ] ) - 1,
                                             int.Parse( indices2s[ 1 ] ) - 1,
                                             int.Parse( indices2s[ 2 ] ) - 1
                                         };
                        string[] indices3s = lineParts[ 3 ].Split( '/' );
                        int[] indices3 = {
                                             int.Parse( indices3s[ 0 ] ) - 1,
                                             int.Parse( indices3s[ 1 ] ) - 1,
                                             int.Parse( indices3s[ 2 ] ) - 1
                                         };
                        triangleIndices.Add(
                            new TriangleIndex(
                                new VertexIndex( indices1 ),
                                new VertexIndex( indices2 ),
                                new VertexIndex( indices3 )
                                ) );
                        break;
                }
            }

            result.Vertices = vertexList.ToArray();
            result.Normals = normalList.ToArray();
            result.TexCoords = texCoordList.ToArray();
            result.TriangleIndices = triangleIndices.ToArray();

            return result;
        }

        public static int UploadTexture( string filename )
        {
            int texture = GL.GenTexture();
            GL.BindTexture( TextureTarget.Texture2D, texture );

            Bitmap bmp = new Bitmap( filename );

            System.Drawing.Imaging.BitmapData data = bmp.LockBits( new Rectangle( 0, 0, bmp.Width, bmp.Height ),
                System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb );

            GL.TexImage2D( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bmp.Width, bmp.Height, 0,
                PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0 );

            bmp.UnlockBits( data );

            GL.TexParameter( TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear );
            GL.TexParameter( TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear );

            return texture;
        }

        static class GLU
        {
            [DllImport( "glu32", EntryPoint = "gluLookAt" )]
            public static extern void LookAt( double eyex, double eyey, double eyez, double centerx, double centery, double centerz, double upx, double upy, double upz );

            [DllImport( "glu32", EntryPoint = "gluPerspective" )]
            public static extern void Perspective( double fov, double aspect, double zNear, double zFar );

            [DllImport( "glu32", EntryPoint = "gluUnProject" )]
            public static extern int UnProject( double winx, double winy, double winz, double[] modelViewMatrix, double[] projectionMatrix, int[] viewPort, double[] posX, double[] posY, double[] posZ );

            [DllImport( "glu32", EntryPoint = "gluNewQuadric" )]
            public static extern IntPtr NewQuadric();

            [DllImport( "glu32", EntryPoint = "gluSphere" )]
            public static extern void Sphere( IntPtr quadric, double radius, int slices, int stacks );

            [DllImport( "glu32", EntryPoint = "gluCylinder" )]
            public static extern void Cylinder( IntPtr quad, double baseRadius, double topRadius, double height, int slices, int stacks );

            [DllImport( "glu32", EntryPoint = "gluDisk" )]
            public static extern void Disk( IntPtr quad, double innerRadius, double outerRadius, int slices, int loops );

            [DllImport( "glu32", EntryPoint = "gluDeleteQuadric" )]
            public static extern void DeleteQuadric( IntPtr quadric );
        }
    }
}
